Newer
Older
taehui / qwilight-fe / src / app / [language] / site / components / SiteYellsView.tsx
@Taehui Taehui on 6 Apr 7 KB 2024-04-07 오전 8:25
import AvatarView from "@/app/[language]/site/components/AvatarView";
import SiteInput from "@/app/[language]/site/components/SiteInput";
import scss from "@/app/[language]/site/components/SiteYellsView.module.scss";
import SiteYellView from "@/app/[language]/site/components/SiteYellView";
import wsAPI from "@/app/[language]/site/lib/wsAPI";
import { SiteView } from "@/app/[language]/site/state/setSiteStore";
import { OnSiteYellInput } from "@/app/[language]/site/type";
import { useSiteStore } from "@/state/Stores";
import { getDefaultAvatarID } from "@/utilities/Utility";
import { observer } from "mobx-react-lite";
import { useTranslations } from "next-intl";
import { useRouter } from "next/navigation";
import { useEffect, useLayoutEffect, useRef, useState } from "react";
import Alert from "react-bootstrap/Alert";
import ListGroup from "react-bootstrap/ListGroup";
import ListGroupItem from "react-bootstrap/ListGroupItem";
import Offcanvas from "react-bootstrap/Offcanvas";
import OffcanvasBody from "react-bootstrap/OffcanvasBody";
import OffcanvasHeader from "react-bootstrap/OffcanvasHeader";
import Stack from "react-bootstrap/Stack";
import { Item, ItemParams, Menu, useContextMenu } from "react-contexify";
import { toast } from "react-toastify";
import { useWindowArea } from "taehui-ts/fe-utilities";

const EventPB = require("@/Event_pb");

export default observer(({ siteView }: { siteView: SiteView }) => {
  const {
    siteID,
    siteName,
    siteYellItems,
    siteNotify,
    isPendingSiteYellOpened,
    lastPendingSiteYell,
    isAvatarsViewOpened,
    avatars,
    isSiteHand,
    setSiteYellsViewLowest,
    setPendingSiteYellOpened,
    setAvatarsViewOpened,
    setSiteYellsView,
  } = siteView;

  const { titleView, targetSiteID } = useSiteStore();

  const { push } = useRouter();

  const t = useTranslations();

  const siteYellsView = useRef<HTMLDivElement>(null);
  const siteInputView = useRef<HTMLDivElement>(null);

  const [siteYellsViewHeight, setSiteYellsViewHeight] = useState("");

  const { windowLength, windowHeight } = useWindowArea();

  useLayoutEffect(() => {
    const tabs = document.getElementById("tabsView");
    const title = titleView?.current;
    const siteInput = siteInputView.current;
    if (title && tabs && siteInput) {
      const { height: titleHeight } = title.getBoundingClientRect();
      const { height: tabsHeight } = tabs.getBoundingClientRect();
      const { height: siteInputHeight } = siteInput.getBoundingClientRect();
      setSiteYellsViewHeight(
        `${windowHeight - 19.5 * 2 - titleHeight - tabsHeight - 2 * 2 - siteInputHeight}px`,
      );
    }
  }, [titleView, windowLength, windowHeight, targetSiteID]);

  const { show: viewSiteYellInput } = useContextMenu({
    id: "siteYell",
  });
  const { show: viewAvatarInput } = useContextMenu({
    id: "avatar",
  });

  const onViewAvatar = ({ props: { avatarID } }: ItemParams) => {
    if (avatarID.startsWith("*")) {
      toast.warning(t("notAvatarViewFault"));
    } else {
      push(`/avatar/!${getDefaultAvatarID(avatarID)}`);
    }
  };

  const onSiteYellInput: OnSiteYellInput = (event, avatarID) => {
    viewSiteYellInput({ event, props: { avatarID } });
  };

  useEffect(() => {
    setSiteYellsView(siteYellsView);
  }, [setSiteYellsView]);

  useEffect(() => {
    const { current } = siteYellsView;

    if (current) {
      const onSiteYellsViewMove = () => {
        const { current } = siteYellsView;
        if (current) {
          const siteYellID = siteYellItems[0]?.siteYellID;
          if (siteYellID && siteYellID > 0 && current.scrollTop === 0) {
            wsAPI.send({
              eventID: EventPB.Event.EventID.GET_SITE_YELLS,
              text: JSON.stringify({
                siteID,
                siteYellID: siteYellID,
              }),
            });
          }

          const { height } = current.getBoundingClientRect();

          const isSiteYellsViewLowest =
            Math.ceil(current.scrollTop + height) >= current.scrollHeight;
          setSiteYellsViewLowest(isSiteYellsViewLowest);
          if (isSiteYellsViewLowest) {
            setPendingSiteYellOpened(false);
          }
        }
      };

      current.addEventListener("scroll", onSiteYellsViewMove);

      return () => {
        current.removeEventListener("scroll", onSiteYellsViewMove);
      };
    }
  }, [setPendingSiteYellOpened, setSiteYellsViewLowest, siteID, siteYellItems]);

  return (
    <>
      <Alert show={isPendingSiteYellOpened} className={`m-4 ${scss.alarm}`}>
        {lastPendingSiteYell && (
          <SiteYellView
            siteYellItem={lastPendingSiteYell}
            onSiteYellInput={onSiteYellInput}
          />
        )}
      </Alert>

      <Alert show={!!siteNotify} className={`m-4 ${scss.alarm}`}>
        {siteNotify}
      </Alert>

      <Stack gap={2}>
        <div
          ref={siteYellsView}
          className={scss.siteView}
          style={{ height: siteYellsViewHeight }}
        >
          <Stack gap={2} className="m-4">
            {siteYellItems.map((siteYellItem) => (
              <SiteYellView
                key={siteYellItem.siteYellID}
                siteYellItem={siteYellItem}
                onSiteYellInput={onSiteYellInput}
              />
            ))}
          </Stack>
        </div>

        <SiteInput siteView={siteView} ref={siteInputView} />
      </Stack>

      <Menu id="siteYell">
        <Item onClick={onViewAvatar}>{t("viewAvatarView")}</Item>
      </Menu>

      <Offcanvas
        placement="end"
        show={isAvatarsViewOpened}
        onHide={() => {
          setAvatarsViewOpened(false);
        }}
      >
        <OffcanvasHeader closeButton>
          <Offcanvas.Title>
            {siteName} ({t("avatarCountText", { avatarCount: avatars.length })})
          </Offcanvas.Title>
        </OffcanvasHeader>
        <OffcanvasBody>
          <ListGroup>
            {avatars.map(
              ({
                avatarID,
                avatarName,
                avatarConfigure,
                isSiteHand,
                isMe,
                isValve,
                isAudioInput,
              }) => (
                <ListGroupItem key={avatarID} active={isMe}>
                  <AvatarView
                    avatarID={avatarID}
                    avatarName={avatarName}
                    avatarConfigure={avatarConfigure}
                    isSiteHand={isSiteHand}
                    isValve={isValve}
                    isAudioInput={isAudioInput}
                    onAvatarInput={(event, avatarID) => {
                      viewAvatarInput({ event, props: { avatarID } });
                    }}
                  />
                </ListGroupItem>
              ),
            )}
          </ListGroup>
        </OffcanvasBody>

        <Menu id="avatar">
          <Item
            disabled={!isSiteHand}
            onClick={({ props: { avatarID } }) => {
              wsAPI.send({
                eventID: EventPB.Event.EventID.SET_SITE_OWNER,
                text: JSON.stringify({
                  siteID,
                  avatarID,
                }),
              });
            }}
          >
            {t("setSiteHand")}
          </Item>
          <Item onClick={onViewAvatar}>{t("viewAvatarView")}</Item>
          <Item
            onClick={({ props: { avatarID } }) => {
              wsAPI.send({
                eventID: EventPB.Event.EventID.NEW_SILENT_SITE,
                text: avatarID,
              });
            }}
          >
            {t("silentSiteNew")}
          </Item>
          <Item
            disabled={!isSiteHand}
            onClick={({ props: { avatarID } }) => {
              wsAPI.send({
                eventID: EventPB.Event.EventID.EXILE_AVATAR,
                text: JSON.stringify({
                  siteID,
                  avatarID,
                }),
              });
            }}
          >
            {t("exileAvatar")}
          </Item>
        </Menu>
      </Offcanvas>
    </>
  );
});